require 'socket'
pcall(require,"znetevent")
require 'zdc.logger'
local pairs,insert=pairs,table.insert
module(..., package.seeall)

local React = {}
local React_mt = {__metatable = {}, __index = React}

local EV_READ = 2
local EV_WRITE = 4
local EV_TIMEOUT = 1

function new(params)
	params=params or {}
	local obj = setmetatable({readset={},writeset={},wdog={},logger=params.logger or zdc.logger.defaultlog}, React_mt)
	obj.st=os.time()
	obj.EV_READ=EV_READ
	obj.EV_WRITE=EV_WRITE
	obj.EV_TIMEOUT=EV_TIMEOUT
	obj.EdgeTriger=0x7fffffff
	return obj
end
if not znetevent then
print("WARN: use luasocket for recv not work well on mac")
function React.sendfd(fd,buf,i,j)
	return fd:send(buf,i,j)
end
function React.recvfd(fd,i)
	return fd:receive(i)
end
else
function React.sendfd(fd,buf,i,j)
	return znetevent.sendfd(fd,buf,i,j)
end
function React.recvfd(fd,i)
	return znetevent.recvfd(fd,i)
end
end
function React:pcallcb(exch,p1,cb,event)
	--local r,v1,v2=pcall(cb,p1,event)
	local r,v1,v2=xpcall(function()return cb(p1,event)end,debug.traceback)
	if r then return v1,v2 end
	self.logger:error(v1)
	r,v1=pcall(exch,p1)
	if not r then
		self.logger:error(v1)
	end
	return -1
end
function React:CloseCB(cbe)
	--if cbe[1] then cbe[1][cbe[2]]=nil end
	--if cbe[3] then cbe[3][cbe[4]]=nil end
end
function React:AddEventCB(exch,p1,fd,event,cb,tm)
	local rb
	local function __e (event) return self:pcallcb(exch,p1,cb,event) end
	if event==EV_READ then
		self.readset[fd]=__e
		rb={self.readset,fd}
	elseif event==EV_WRITE then
		self.writeset[fd]=__e
		rb={self.writeset,fd}
	elseif event==EV_TIMEOUT then
		self.wdog[__e]=tm
		rb={self.wdog,__e}
	else
		error('unknow event' .. tostring(event))
	end
	if tm then
		self.wdog[__e]=tm
		rb[3]=self.wdog
		rb[4]=__e
	end
	return rb
end


function React:Dispatch(timeout)
	timeout=timeout or 10
	local ro,wo=self.readset,self.writeset
	local rs,ws={},{}
	for k,v in pairs(ro) do
		insert(rs,k)
	end
	for k,v in pairs(wo) do
		insert(ws,k)
	end
	local r,w,err=socket.select(rs,ws,timeout)
	if err and err~='timeout' then
		error(err)
	end
	for i=1,#r do
		local fd=r[i]
		local hd=ro[fd]
		if hd then
			local r1,r2=hd(EV_READ)
			if r1==-1 then
				if ro[fd]==hd then ro[fd]=nil end
				self.wdog[hd]=nil
			else
				
			end
		end
	end
	for i=1,#w do
		local fd=w[i]
		local hd=wo[fd]
		if hd then
			local r1,r2=hd(EV_WRITE)
			if r1==-1 then
				if wo[fd]==hd then wo[fd]=nil end
				self.wdog[hd]=nil
			else
			end
		end
	end
	local et=os.time()
	if et>=self.st+1 then
		self.st=et
		local tl={}
		for k,v in pairs(self.wdog) do
			self.wdog[k]=v-1
			if self.wdog[k]<=0 then
				insert(tl,k)
			end
		end
		for i=1,#tl do
			local hd=tl[i]
			if hd then
				local r1,r2=hd(EV_TIMEOUT)
				if r1==-1 then
					self.wdog[hd]=nil
				else
					self.wdog[hd]=r2
				end
			end
		end
	end
end
